Add APNG Studio canvas extension#2272
Open
AndreaGriffiths11 wants to merge 7 commits into
Open
Conversation
APNG Studio is an interactive GitHub Copilot canvas extension for building Animated PNG (APNG) files from frames: draw or upload frames, tune per-frame timing and compositing, preview live, send the result to a phone by QR, and export an animated .png. Adds extensions/apng-studio/ with the required .github/plugin/plugin.json and assets/preview.png, and regenerates .github/plugin/marketplace.json. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
|
🔴 Contributor Reputation Check: HIGH risk
Maintainers: please review this contributor before merging. |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds APNG Studio, a canvas extension for creating, previewing, sharing, and exporting animated PNGs.
Changes:
- Adds APNG and QR encoders with persistent project storage.
- Adds the interactive canvas UI and agent actions.
- Registers the extension in the marketplace.
Show a summary per file
| File | Description |
|---|---|
.github/plugin/marketplace.json |
Registers APNG Studio. |
extensions/apng-studio/.github/plugin/plugin.json |
Defines extension metadata. |
extensions/apng-studio/LICENSE |
Adds the MIT license. |
extensions/apng-studio/README.md |
Documents features and installation. |
extensions/apng-studio/apng.mjs |
Implements PNG/APNG encoding. |
extensions/apng-studio/assets/demo.png |
Provides the README demonstration. |
extensions/apng-studio/assets/preview.png |
Provides the marketplace preview. |
extensions/apng-studio/copilot-extension.json |
Defines runtime metadata. |
extensions/apng-studio/extension.mjs |
Implements storage, actions, servers, and sharing. |
extensions/apng-studio/qr.mjs |
Implements QR encoding. |
extensions/apng-studio/web/app.js |
Implements client-side behavior. |
extensions/apng-studio/web/index.html |
Defines the canvas interface. |
extensions/apng-studio/web/styles.css |
Styles the canvas interface. |
Review details
- Files reviewed: 11/13 changed files
- Comments generated: 9
- Review effort level: Medium
Security and robustness (extension.mjs):
- Reject "." and ".." project ids so canvas/action input cannot resolve
outside artifacts/.
- Bound request bodies (1 MiB JSON, 40 MiB frame upload); return 413 when
exceeded.
- Reject malformed JSON with 400 instead of coercing to {}, so a truncated
body cannot trigger destructive routes such as /frames/clear.
- Scope shares per project so one canvas cannot rotate or stop another's
share; the LAN server is shared and torn down once no shares remain.
- get_state treats hiddenFirst as active only with >=2 frames, matching the
encoder, so a single-frame project reports the correct duration.
Accessibility (web/):
- Expose Pen/Eraser state with aria-pressed and keep it in sync in setTool.
- Mark the toast as an aria-live status region.
Packaging:
- Add extensions/apng-studio/.gitignore with artifacts/ so the documented
runtime-data exclusion holds for this extension.
- Update the README install section to reference the committed awesome-copilot
extension path.
Regenerated .github/plugin/marketplace.json.
Update the plugin description (and regenerated marketplace entry) to refer to the host as the GitHub Copilot app.
Security: - Require a per-server access token on every loopback data/mutation request (minted per canvas server, carried in the iframe URL, attached to every renderer request). Static assets stay public. Blocks a local process or cross-origin page from reading state or driving mutations. Concurrency and durability: - Serialize the load-mutate-save cycle per project; dedupe concurrent first loads; write project.json via temp file + rename. Lifecycle: - Track SSE clients per instance and end them before server.close(). - Stop a project's LAN share only when no other panel references it. Correctness: - Report exact numerator/denominator total duration; handle pointercancel. QR and accessibility: - Place QR version bits least-significant-bit first (versions 7-10). - Associate delay/fps/dispose/blend labels with their inputs via for=.
- Run assemble() under the project lock; add_color_frame renders and appends within one lock. - Validate uploaded frames (PNG chunk scan, size + dimension checks) before writing; first frame stores real dimensions; CanvasError maps to 400. - Validate move delta; clear frames in memory before deleting files. - Route the open-handler rename through applySettings. - Deduplicate LAN share startup; clean up if the canvas closes mid-start. - End SSE streams before server.close(); skip dead streams in broadcast. - Exact fractional duration; drawing surface stays >= 1px; refresh state before re-seeding "start from last frame".
- Validate uploaded frames are 8-bit RGBA non-interlaced PNGs (standard compression/filter); reject formats the codec can't encode. - Cap projects at 600 frames (add + duplicate). - Bind the LAN share server to the private address, not 0.0.0.0; require a private address and rebind when it changes while idle; build the URL from the bound address. - Align width/height inputs and clampSize to the 2048 maximum. - Size the drawing surface by width + aspect ratio for narrow panels. - set_frame with an unknown frameId returns a validation error.
- 256 MiB aggregate encoded-byte budget (add + duplicate); per-frame size tracked and backfilled on load, so a frame count alone no longer bounds assembly memory. - Frame timing is one exclusive mode (delayMs | fps | delayNum/delayDen); combinations are rejected instead of producing hybrid delays. - Collision-resistant project storage keys: safe ids are used verbatim (existing projects still load) and only unsafe ids get a hash suffix, so "foo/bar" and "foo?bar" can't share a directory. - Load only treats a missing file as a new project; other read/parse errors surface instead of silently overwriting real data. - Share server binds to a real LAN interface (skips virtual/VPN/container, prefers physical NICs and common ranges). - Generated export names get millisecond + random suffix to avoid same-second overwrite. - PNG validator accepts the encoder's Uint8Array so agent solid-color frames aren't rejected.
Comment on lines
+318
to
+321
| meta.frames.splice(i, 1); | ||
| await fs.rm(framePath(meta.id, fid), { force: true }); | ||
| await saveProject(meta); | ||
| broadcast(meta.id); |
Comment on lines
+1162
to
+1164
| entry = { instanceId: ctx.instanceId, projectId, sse: new Set() }; | ||
| servers.set(ctx.instanceId, entry); | ||
| await startServer(entry); |
Comment on lines
+180
to
+185
| return { | ||
| delayNum, | ||
| delayDen, | ||
| disposeOp: clampOp(f.disposeOp, 2), | ||
| blendOp: clampOp(f.blendOp, 1), | ||
| }; |
Comment on lines
+443
to
+445
| const first = await createImageBitmap(files[0]); | ||
| const { w, h } = clampSize(first.width, first.height); | ||
| await api("/settings", { width: w, height: h }); |
Comment on lines
+456
to
+460
| const bmp = await createImageBitmap(file); | ||
| const c = document.createElement("canvas"); | ||
| c.width = state.width; | ||
| c.height = state.height; | ||
| drawContain(c.getContext("2d"), bmp, state.width, state.height); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
APNG Studio is an interactive GitHub Copilot canvas extension for building Animated PNG (APNG) files from frames. It renders in a side panel and is also driveable through agent actions.
Features:
dispose_opandblend_opfrom the APNG spec..png(byte-compatible with PNG, so every viewer can open it).The APNG codec and a dependency-free QR encoder are vendored (
apng.mjs,qr.mjs); no third-party runtime dependencies.Contribution type
Canvas extension, added at
extensions/apng-studio/with.github/plugin/plugin.jsonandassets/preview.png.Validation
npm run plugin:validate: extension apng-studio is valid.npm start: regenerated.github/plugin/marketplace.json; README requires no changes.